Ext.define('Zm.state.HttpProvider', {
    extend: 'Ext.state.Provider',
    requires:['Ext.util.DelayedTask'],
    delay:750, // buffer changes for 750 ms
    dirty:false,
    started:false,
    autoStart:true,
    autoRead:true,
    user:'user',
    id:1,
    session:'session',
    logFailure:false,
    logSuccess:false,
    queue:[],
    url:'.',
    readUrl:undefined,
    saveUrl:undefined,
    method:'post',
    saveBaseParams:{},
    readBaseParams:{},
    paramNames:{
        id:'id',
        name:'name',
        value:'value',
        user:'user',
        session:'session',
        data:'data'
    },
    saveSuccessText:'Save Success',
    saveFailureText:'Save Failure',
    readSuccessText:'Read Success',
    readFailureText:'Read Failure',
    dataErrorText:'Data Error',
    constructor : function(config){
        var me = this;
        me.addEvents(
            /**
             * @event readsuccess
             * Fires after state has been successfully received from server and restored
             * @param {HttpProvider} this
             */
             'readsuccess'
            /**
             * @event readfailure
             * Fires in the case of an error when attempting to read state from server
             * @param {HttpProvider} this
             */
            ,'readfailure'
            /**
             * @event savesuccess
             * Fires after the state has been successfully saved to server
             * @param {HttpProvider} this
             */
            ,'savesuccess'
            /**
             * @event savefailure
             * Fires in the case of an error when attempting to save state to the server
             * @param {HttpProvider} this
             */
            ,'savefailure'
        );
        me.callParent(arguments);
        if(me.autoRead) {
            me.readState();
        }

        me.dt = new Ext.util.DelayedTask(me.submitState, me);
        if(me.autoStart) {
            me.start();
        }
    },
    /**
     * Initializes state from the passed state object or array.
     * This method can be called early during page load having the state Array/Object
     * retrieved from database by server.
     * @param {Array/Object} state State to initialize state manager with
     */
    initState:function(state) {
        var me = this;
        if(state instanceof Array) {
            Ext.each(state, function(item) {
                me.state[item.name] = me.decodeValue(item[me.paramNames.value]);
            }, me);
        }
        else {
            me.state = state ? state : {};
        }
    },
    /**
     * Sets the passed state variable name to the passed value and queues the change
     * @param {String} name Name of the state variable
     * @param {Mixed} value Value of the state variable
     */
    set:function(name, value) {
        if(!name) {
            return;
        }

        this.queueChange(name, value);

    } // eo function set
    // }}}
    // {{{
    /**
     * Starts submitting state changes to server
     */
    ,start:function() {
        var me = this;
        me.dt.delay(me.delay);
        me.started = true;
    } // eo function start
    // }}}
    // {{{
    /**
     * Stops submitting state changes
     */
    ,stop:function() {
        var me = this;
        me.dt.cancel();
        me.started = false;
    } // eo function stop
    // }}}
    // {{{
    /**
     * private, queues the state change if state has changed
     */
    ,queueChange:function(name, value) {
        var me = this,
            o = {},
            i,
            found = false,
            lastValue = me.state[name];
        for(i = 0; i < me.queue.length; i++) {
            if(me.queue[i].name === name) {
                lastValue = me.decodeValue(me.queue[i].value);
            }
        }
        var changed = undefined === lastValue || lastValue !== value;

        if(changed) {
            o[me.paramNames.name] = name;
            o[me.paramNames.value] = me.encodeValue(value);
            for(i = 0; i < me.queue.length; i++) {
                if(me.queue[i].name === o.name) {
                    me.queue[i] = o;
                    found = true;
                }
            }
            if(false === found) {
                me.queue.push(o);
            }
            me.dirty = true;
        }
        if(me.started) {
            me.start();
        }
        return changed;
    } // eo function bufferChange
    // }}}
    // {{{
    /**
     * private, submits state to server by asynchronous Ajax request
     */
    ,submitState:function() {
        var me = this;
        if(!me.dirty) {
            me.dt.delay(me.delay);
            return;
        }
        me.dt.cancel();

        var o = {
             url:me.saveUrl || me.url
            ,method:me.method
            ,scope:me
            ,success:me.onSaveSuccess
            ,failure:me.onSaveFailure
            ,queue:Ext.clone(me.queue)
            ,params:{}
        };

        var params = Ext.apply({}, me.saveBaseParams);
        params[me.paramNames.id] = me.id;
        params[me.paramNames.user] = me.user;
        params[me.paramNames.session] = me.session;
        params[me.paramNames.data] = Ext.encode(o.queue);

        Ext.apply(o.params, params);

        // be optimistic
        me.dirty = false;

        Ext.Ajax.request(o);
    } // eo function submitState
    // }}}
    // {{{
    /**
     * Clears the state variable
     * @param {String} name Name of the variable to clear
     */
    ,clear:function(name) {
        this.set(name, undefined);
    } // eo function clear
    // }}}
    // {{{
    /**
     * private, save success callback
     */
    ,onSaveSuccess:function(response, options) {
        var me = this,o = {};
        try {o = Ext.decode(response.responseText);}
        catch(e) {
            if(true === me.logFailure) {
                me.log(me.saveFailureText, e, response);
            }
            me.dirty = true;
            return;
        }
        if(me !== o.success) {
            if(true === me.logFailure) {
                me.log(me.saveFailureText, o, response);
            }
            me.dirty = true;
        }
        else {
            Ext.each(options.queue, function(item) {
                if(!item) {
                    return;
                }
                var name = item[me.paramNames.name];
                var value = me.decodeValue(item[me.paramNames.value]);

                if(undefined === value || null === value) {
                    Zm.state.HttpProvider.superclass.clear.call(me, name);
                }
                else {
                    // parent sets value and fires event
                    Zm.state.HttpProvider.superclass.set.call(me, name, value);
                }
            }, me);
            if(false === me.dirty) {
                me.queue = [];
            }
            else {
                var i, j, found;
                for(i = 0; i < options.queue.length; i++) {
                    found = false;
                    for(j = 0; j < me.queue.length; j++) {
                        if(options.queue[i].name === me.queue[j].name) {
                            found = true;
                            break;
                        }
                    }
                    if(true === found && me.encodeValue(options.queue[i].value) === me.encodeValue(me.queue[j].value)) {
                        me.queue.remove(me.queue[j]);
                    }
                }
            }
            if(true === me.logSuccess) {
                me.log(me.saveSuccessText, o, response);
            }
            me.fireEvent('savesuccess', me);
        }
    } // eo function onSaveSuccess
    // }}}
    // {{{
    /**
     * private, save failure callback
     */
    ,onSaveFailure:function(response, options) {
        var me =this;
        if(true === me.logFailure) {
            me.log(me.saveFailureText, response);
        }
        me.dirty = true;
        me.fireEvent('savefailure', me);
    } // eo function onSaveFailure
    // }}}
    // {{{
    /**
     * private, read state callback
     */
    ,onReadFailure:function(response, options) {
        var me = this;
        if(true === me.logFailure) {
            me.log(me.readFailureText, response);
        }
        me.fireEvent('readfailure', me);

    } // eo function onReadFailure
    // }}}
    // {{{
    /**
     * private, read success callback
     */
    ,onReadSuccess:function(response, options) {
        var me = this, o = {}, data;
        try {o = Ext.decode(response.responseText);}
        catch(e) {
            if(true === me.logFailure) {
                me.log(me.readFailureText, e, response);
            }
            return;
        }
        if(true !== o.success) {
            if(true === me.logFailure) {
                me.log(me.readFailureText, o, response);
            }
        }
        else {
            data = o[me.paramNames.data];
            if(!(data instanceof Array) && true === me.logFailure) {
                me.log(me.dataErrorText, data, response);
                return;
            }
            Ext.each(data, function(item) {
                me.state[item.name] = me.decodeValue(item.value);
            }, me);
            me.queue = [];
            me.dirty = false;
            if(true === me.logSuccess) {
                me.log(me.readSuccessText, data, response);
            }
            me.fireEvent('readsuccess', me);
        }
    } // eo function onReadSuccess
    // }}}
    // {{{
    /**
     * Reads saved state from server by sending asynchronous Ajax request and processing the response
     */
    ,readState:function() {
        var me = this,
            o = {
             url:me.readUrl || me.url
            ,method:me.method
            ,scope:me
            ,success:me.onReadSuccess
            ,failure:me.onReadFailure
            ,params:{}
        };

        var params = Ext.apply({}, me.readBaseParams);
        params[me.paramNames.id] = me.id;
        params[me.paramNames.user] = me.user;
        params[me.paramNames.session] = me.session;

        Ext.apply(o.params, params);
        Ext.Ajax.request(o);
    } // eo function readState
    // }}}
    // {{{
    /**
     * private, logs errors or successes
     */
    ,log:function() {
        if(console) {
            console.log.apply(console, arguments);
        }
    } // eo log
});